/*
 * Copyright 2016 Stuart Kent
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License.
 *
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package com.example.mybugshaker;

import com.example.mybugshaker.flow.dialog.AppCompatDialogProvider;
import com.example.mybugshaker.flow.dialog.DialogProvider;
import com.example.mybugshaker.flow.dialog.IDialogType;
import com.example.mybugshaker.flow.dialog.NativeDialogProvider;
import com.example.mybugshaker.flow.email.EmailCapabilitiesProvider;
import com.example.mybugshaker.flow.email.FeedbackEmailFlowManager;
import com.example.mybugshaker.flow.email.FeedbackEmailIntentProvider;
import com.example.mybugshaker.flow.email.GenericEmailIntentProvider;
import com.example.mybugshaker.flow.email.screenshot.BasicScreenShotProvider;
import com.example.mybugshaker.flow.email.screenshot.ScreenshotProvider;
import com.example.mybugshaker.utils.Logger;
import com.example.mybugshaker.utils.Toaster;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.ability.AbilityPackage;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.aafwk.content.Operation;
import ohos.agp.components.DirectionalLayout;
import ohos.agp.components.LayoutScatter;
import ohos.agp.window.dialog.CommonDialog;
import ohos.agp.window.dialog.IDialog;
import ohos.app.ElementsCallback;
import ohos.global.configuration.Configuration;
import ohos.rpc.RemoteException;
import ohos.sensor.agent.CategoryMotionAgent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import static ohos.agp.components.ComponentContainer.LayoutConfig.MATCH_CONTENT;
/**
 * The main interaction point for library users. Encapsulates all shake detection. Setters allow users to customize some
 * aspects (recipients, subject line) of bug report emails.
 */
public final class BugShaker implements ShakeDetector.Listener{
    private static final int THROSAND = 1000;

    public static AbilitySlice getAbilitySlice() {
        return abilitySlice;
    }

    public static void setAbilitySlice(AbilitySlice sabilitySlice) {
        BugShaker.abilitySlice = sabilitySlice;
    }

    private  static  AbilitySlice abilitySlice ;

    private static final IDialogType DEFAULT_ALERT_DIALOG_TYPE = IDialogType.NATIVE;

    private static final String RECONFIGURATION_EXCEPTION_MESSAGE =
            "Configuration must be completed before calling assemble or start.";

    private static final String DIALOG_CONFIGURATION_CONFLICT_EXCEPTION_MESSAGE =
            "You may call either setAlertDialogType or setCustomDialogProvider, but not both.";

    //    @SuppressLint("StaticFieldLeak") // we're holding the application context.
    private static BugShaker sharedInstance;

    private final AbilityPackage application;
    private EmailCapabilitiesProvider emailCapabilitiesProvider;

    public FeedbackEmailFlowManager getFeedbackEmailFlowManager() {
        return feedbackEmailFlowManager;
    }

    public void setFeedbackEmailFlowManager(FeedbackEmailFlowManager sfeedbackEmailFlowManager) {
        this.feedbackEmailFlowManager = sfeedbackEmailFlowManager;
    }

    private FeedbackEmailFlowManager feedbackEmailFlowManager;
    private Logger logger;
    private String[] emailAddresses;
    private String emailSubjectLine;
    @Nullable
    private IDialogType alertDialogType;
    @Nullable
    private DialogProvider customDialogProvider;
    private boolean ignoreFlagSecure = false;
    private boolean loggingEnabled = false;

    // Instance configuration state:
    private boolean assembled = false;
    private boolean startAttempted = false;
    private ElementsCallback elementsCallback = new ElementsCallback() {
        @Override
        public void onMemoryLevel(int i) {

        }

        @Override
        public void onConfigurationUpdated(Configuration configuration) {

        }
    };

    private final SimpleActivityLifecycleCallback simpleActivityLifecycleCallback = new SimpleActivityLifecycleCallback() {
        @Override
        public void onAbilityInactive(final Ability ability) {

        }

        @Override
        public void onAbilityActive(Ability ability) {
            feedbackEmailFlowManager.onActivityResumed(ability);
        }

        @Override
        public void onAbilityStop(final Ability ability) {
            feedbackEmailFlowManager.onActivityStopped();
        }

    };
    private boolean isfirst=true;

    @NotNull
    public static BugShaker get(@NotNull final  AbilitySlice fabilitySlice) {

        synchronized (BugShaker.class) {
            if (sharedInstance == null) {
                //BugShaker.abilitySlice=abilitySlice;
                BugShaker.setAbilitySlice(fabilitySlice);
                AbilityPackage application= (AbilityPackage) fabilitySlice.getAbility().getAbilityPackage();
                sharedInstance = new BugShaker(application);
            }
        }
        return sharedInstance;
    }

    //    @VisibleForTesting
    public static void clearSharedInstance() {
        sharedInstance = null;
    }

    public BugShaker(@NotNull final AbilityPackage abilityPackage) {
        this.application = abilityPackage;
    }

    /**
     * (Required) Defines one or more email addresses to send bug reports to. This method MUST be called before calling
     * <code>assemble</code>. This method CANNOT be called after calling <code>assemble</code> or <code>start</code>.
     *
     * @param semailAddresses one or more email addresses
     * @return the current <code>BugShaker</code> instance (to allow for method chaining)
     */
    @NotNull
    public BugShaker setEmailAddresses(@NotNull final String... semailAddresses) {
        if (assembled || startAttempted) {
            throw new IllegalStateException(
                    "Configuration must be complete before calling assemble or start");
        }

        this.emailAddresses = semailAddresses.clone();
        return this;
    }

    /**
     * (Optional) Defines a custom subject line to use for all bug reports. By default, reports will use the string
     * defined in <code>DEFAULT_SUBJECT_LINE</code>. This method CANNOT be called after calling <code>assemble</code> or
     * <code>start</code>.
     *
     * @param semailSubjectLine a custom email subject line
     * @return the current <code>BugShaker</code> instance (to allow for method chaining)
     */
    @NotNull
    public BugShaker setEmailSubjectLine(@NotNull final String semailSubjectLine) {
        if (assembled || startAttempted) {
            throw new IllegalStateException(RECONFIGURATION_EXCEPTION_MESSAGE);
        }

        this.emailSubjectLine = semailSubjectLine;
        return this;
    }

    /**
     * (Optional) Defines a dialog type (native/material) to present when a shake is detected. Native dialogs are used
     * by default. This method CANNOT be called after calling <code>assemble</code> or <code>start</code>.
     *
     * @param salertDialogType the dialog type to present
     * @return the current <code>BugShaker</code> instance (to allow for method chaining)
     */
    @NotNull
    public BugShaker setAlertDialogType(@NotNull final IDialogType salertDialogType) {
        if (assembled || startAttempted) {
            throw new IllegalStateException(RECONFIGURATION_EXCEPTION_MESSAGE);
        }

        if (customDialogProvider != null) {
            throw new IllegalStateException(DIALOG_CONFIGURATION_CONFLICT_EXCEPTION_MESSAGE);
        }

        this.alertDialogType = salertDialogType;
        return this;
    }

    /**
     * (Optional) Sets a custom dialog provider. This provider will be used to build a dialog each time a shake is
     * detected. This method CANNOT be called after calling <code>assemble</code> or <code>start</code>.
     *
     * @param dialogProvider the dialog provider to use
     * @return the current <code>BugShaker</code> instance (to allow for method chaining)
     */
    @NotNull
    public BugShaker setCustomDialogProvider(@NotNull final DialogProvider dialogProvider) {
        if (assembled || startAttempted) {
            throw new IllegalStateException(RECONFIGURATION_EXCEPTION_MESSAGE);
        }

        if (alertDialogType != null) {
            throw new IllegalStateException(DIALOG_CONFIGURATION_CONFLICT_EXCEPTION_MESSAGE);
        }

        customDialogProvider = dialogProvider;
        return this;
    }

    /**
     * (Optional) Enables debug and error log messages. Logging is disabled by default. This method CANNOT be called
     * after calling <code>assemble</code> or <code>start</code>.
     *
     * @param sloggingEnabled true if logging should be enabled; false otherwise
     * @return the current <code>BugShaker</code> instance (to allow for method chaining)
     */
    @NotNull
    public BugShaker setLoggingEnabled(final boolean sloggingEnabled) {
        if (assembled || startAttempted) {
            throw new IllegalStateException(RECONFIGURATION_EXCEPTION_MESSAGE);
        }

        this.loggingEnabled = sloggingEnabled;
        return this;
    }

    /**
     * (Optional) Choose whether to ignore the <code>FLAG_SECURE</code> <code>Window</code> flag when capturing
     * screenshots. This method CANNOT be called after calling <code>assemble</code> or <code>start</code>.
     *
     * @param signoreFlagSecure true if screenshots should be allowed even when <code>FLAG_SECURE</code> is set on the
     *                         current <code>Window</code>; false otherwise
     * @return the current <code>BugShaker</code> instance (to allow for method chaining)
     */
    @NotNull
    public BugShaker setIgnoreFlagSecure(final boolean signoreFlagSecure) {
        if (assembled || startAttempted) {
            throw new IllegalStateException(RECONFIGURATION_EXCEPTION_MESSAGE);
        }

        this.ignoreFlagSecure = signoreFlagSecure;
        return this;
    }

    /**
     * (Required) Assembles dependencies based on provided configuration information. This method CANNOT be called more
     * than once. This method CANNOT be called after calling <code>start</code>.
     *
     * @return the current <code>BugShaker</code> instance (to allow for method chaining)
     */
    @NotNull
    public BugShaker assemble() {
        if (assembled) {
            logger.d("You have already assembled this BugShaker instance. Calling assemble again is a no-op.");
            return this;
        }

        if (startAttempted) {
            throw new IllegalStateException("You can only call assemble before calling start.");
        }

        logger = new Logger(loggingEnabled);

        final GenericEmailIntentProvider genericEmailIntentProvider = new GenericEmailIntentProvider();

        emailCapabilitiesProvider = new EmailCapabilitiesProvider(application,
                application.getBundleManager(),
                genericEmailIntentProvider,
                logger);

        feedbackEmailFlowManager = new FeedbackEmailFlowManager(
                application,
                emailCapabilitiesProvider,
                new Toaster(application),
                new ActivityReferenceManager(),
                new FeedbackEmailIntentProvider(application, genericEmailIntentProvider),
                getScreenshotProvider(),
                getAlertDialogProvider(),
                logger);

        assembled = true;
        return this;
    }

    /**
     * (Required) Start listening for device shaking. You MUST call <code>assemble</code> before calling this method.
     */
    public void start() throws RemoteException {

        if (!assembled) {
            throw new IllegalStateException("You MUST call assemble before calling start.");
        }

        if (startAttempted) {
            logger.d("You have already attempted to start this BugShaker instance. Calling start "
                    + "again is a no-op.");
            return ;
        }

        final ShakeDetector shakeDetector = new ShakeDetector(this);
        CategoryMotionAgent categoryMotionAgent = new CategoryMotionAgent();
        final boolean didStart = shakeDetector.start(categoryMotionAgent);
        if (emailCapabilitiesProvider.canSendEmails()) {

            application.registerCallbacks(simpleActivityLifecycleCallback,elementsCallback);

            if (didStart) {
                logger.d("Shake detection successfully started!");
            } else {
                logger.e("Error starting shake detection: hardware does not support detection.");
            }
        } else {
            logger.e("Error starting shake detection: device cannot send emails.");
        }

        startAttempted = true;
    }

    // 摇一摇出现弹窗
    public void showDialog() {
        DirectionalLayout layout = (DirectionalLayout) LayoutScatter.getInstance(abilitySlice)
                .parse(ResourceTable.Layout_dialog, null, false);
        new CommonDialog(abilitySlice.getContext())
                .setContentCustomComponent(layout)
                .setButton(1, "CANCEL", new IDialog.ClickedListener() {
                    @Override
                    public void onClick(IDialog iDialog, int i) {
                        isfirst=true;
                        iDialog.destroy();
                    }

                })
                .setButton(2, "REPORT", new IDialog.ClickedListener() {
                    @Override
                    public void onClick(IDialog iDialog, int i) {
                        isfirst=true;
                        showEmal();
                        iDialog.destroy();
                        }
                })
                .setSize(THROSAND, MATCH_CONTENT)
                .show();
    }

    // Intent 邮件
    public void showEmal() {
        Intent intent = new Intent();
        Operation builder= new Intent.OperationBuilder().withAction("android.intent.action.SEND").build();
        intent.setOperation(builder);
        intent.setType("message/rfc822");
        intent.setParam("android.intent.extra.EMAIL", BugShaker.this.emailAddresses);
        intent.setParam("android.intent.extra.SUBJECT", emailSubjectLine);
        intent.setParam("android.intent.extra.TEXT", feedbackEmailFlowManager.getFeedbackEmailIntentProvider().getApplicationInfoString());
        abilitySlice.startAbility(intent);

    }

    @Override
    public void hearShake() {
        abilitySlice.getUITaskDispatcher().asyncDispatch(new Runnable() {
            @Override
            public void run() {
                // 调用弹窗
                if (isfirst) {
                showDialog();
                isfirst=false;}
            }
        });
    }

    /**
     * @return a MapScreenshotProvider if the embedding application utilizes the Google Maps API, and a
     *         BasicScreenshotProvider otherwise
     * currently: Google maps are not supported on OpenHarmony
     */
    @NotNull
    public ScreenshotProvider getScreenshotProvider() {

        logger.d("Detected that embedding app does not include Google Maps as a dependency.");

            return new BasicScreenShotProvider(application, logger);

    }

    @NotNull
    public DialogProvider getAlertDialogProvider() {
        if (customDialogProvider != null) {
            return customDialogProvider;
        }

        final IDialogType iDialogType;
        if (this.alertDialogType != null) {
            iDialogType = this.alertDialogType;
        } else {
            iDialogType = DEFAULT_ALERT_DIALOG_TYPE;
        }

        if (iDialogType == IDialogType.APP_COMPAT) {
            try {
                Class.forName(
                        "ohos.agp.window.dialog.ToastDialog",
                        false,
                        BugShaker.class.getClassLoader());

                logger.d("Using AppCompat dialogs as requested.");
                return new AppCompatDialogProvider();
            } catch (final ClassNotFoundException e) {
                logger.e("AppCompat dialogs requested, but class not found.");
                logger.e("Falling back to native dialogs.");
                return new NativeDialogProvider();
            }

        } else {
            logger.d("Using native dialogs as requested.");
            return new NativeDialogProvider();
        }
    }
}
